// Go作者版权所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

// 包基准提供了一个度量对象，该对象支持内存和CPU
// 链接器的分析。度量对象可用于标记阶段
// 并命名该阶段的测量值。还有
// 可在每个阶段结束时执行的可选地面军事系统，因此
// 可以准确测量每个阶段如何改变生活记忆。
package benchmark

import (
	"fmt"
	"io"
	"os"
	"runtime"
	"runtime/pprof"
	"time"
	"unicode"
)

type Flags int

const (
	GC         = 1 << iota
	NoGC Flags = 0
)

type Metrics struct {
	gc        Flags
	marks     []*mark
	curMark   *mark
	filebase  string
	pprofFile *os.File
}

type mark struct {
	name              string
	startM, endM, gcM runtime.MemStats
	startT, endT      time.Time
}

// 新建创建一个新的度量对象。
// None
// 典型用法如下所示：
// None
// func main（）{
// 文件名：=“”
// bench:=benchmark.New（benchmark.GC，文件名）
// 延迟工作台报告（os.Stdout）
// None
// 工作台启动（“foo”）
// foo（）
// 工作台启动（“酒吧”）
// bar（）
// }
// None
// 请注意，nil Metrics对象不会导致任何错误，因此可以编写
// 代码如下：
// None
// func main（）{
// enableBenchmarking:=flag.Bool（“启用”，true，“启用基准测试”）
// flag.Parse（）
// var bench*benchmark.Metrics
// 如果*启用基准测试{
// bench=benchmark.New（benchmark.GC）
// }
// 工作台启动（“foo”）
// None
// }
func New(gc Flags, filebase string) *Metrics {
	if gc == GC {
		runtime.GC()
	}
	return &Metrics{gc: gc, filebase: filebase}
}

// 报告指标。
// 关闭当前开始（ed）范围，并将报告写入给定io.Writer。
func (m *Metrics) Report(w io.Writer) {
	if m == nil {
		return
	}

	m.closeMark()

	gcString := ""
	if m.gc == GC {
		gcString = "_GC"
	}

	var totTime time.Duration
	for _, curMark := range m.marks {
		dur := curMark.endT.Sub(curMark.startT)
		totTime += dur
		fmt.Fprintf(w, "%s 1 %d ns/op", makeBenchString(curMark.name+gcString), dur.Nanoseconds())
		fmt.Fprintf(w, "\t%d B/op", curMark.endM.TotalAlloc-curMark.startM.TotalAlloc)
		fmt.Fprintf(w, "\t%d allocs/op", curMark.endM.Mallocs-curMark.startM.Mallocs)
		if m.gc == GC {
			fmt.Fprintf(w, "\t%d live-B", curMark.gcM.HeapAlloc)
		} else {
			fmt.Fprintf(w, "\t%d heap-B", curMark.endM.HeapAlloc)
		}
		fmt.Fprintf(w, "\n")
	}
	fmt.Fprintf(w, "%s 1 %d ns/op\n", makeBenchString("total time"+gcString), totTime.Nanoseconds())
}

// 开始标志着新测量阶段的开始。
// 一旦开始度量，它将一直持续到发布报告或调用另一个开始。
func (m *Metrics) Start(name string) {
	if m == nil {
		return
	}
	m.closeMark()
	m.curMark = &mark{name: name}
	// 我们不太可能在这里需要一个GC，因为它很可能是在closeMark中完成的。
	if m.shouldPProf() {
		f, err := os.Create(makePProfFilename(m.filebase, name, "cpuprof"))
		if err != nil {
			panic(err)
		}
		m.pprofFile = f
		if err = pprof.StartCPUProfile(m.pprofFile); err != nil {
			panic(err)
		}
	}
	runtime.ReadMemStats(&m.curMark.startM)
	m.curMark.startT = time.Now()
}

func (m *Metrics) closeMark() {
	if m == nil || m.curMark == nil {
		return
	}
	m.curMark.endT = time.Now()
	if m.shouldPProf() {
		pprof.StopCPUProfile()
		m.pprofFile.Close()
		m.pprofFile = nil
	}
	runtime.ReadMemStats(&m.curMark.endM)
	if m.gc == GC {
		runtime.GC()
		runtime.ReadMemStats(&m.curMark.gcM)
		if m.shouldPProf() {
			// 收集活动堆的配置文件。做一个
			// 第二个GC强制扫描完成，所以我们
			// 在上获取活动堆的完整快照
			// 这一阶段的结束。
			runtime.GC()
			f, err := os.Create(makePProfFilename(m.filebase, m.curMark.name, "memprof"))
			if err != nil {
				panic(err)
			}
			err = pprof.WriteHeapProfile(f)
			if err != nil {
				panic(err)
			}
			err = f.Close()
			if err != nil {
				panic(err)
			}
		}
	}
	m.marks = append(m.marks, m.curMark)
	m.curMark = nil
}

// 如果我们应该进行多次运行，shouldprof将返回true。
func (m *Metrics) shouldPProf() bool {
	return m != nil && len(m.filebase) > 0
}

// makeBenchString使基准字符串可由Go的基准测试工具使用。
func makeBenchString(name string) string {
	needCap := true
	ret := []rune("Benchmark")
	for _, r := range name {
		if unicode.IsSpace(r) {
			needCap = true
			continue
		}
		if needCap {
			r = unicode.ToUpper(r)
			needCap = false
		}
		ret = append(ret, r)
	}
	return string(ret)
}

func makePProfFilename(filebase, name, typ string) string {
	return fmt.Sprintf("%s_%s.%s", filebase, makeBenchString(name), typ)
}
